package org.brakepedal.server.support.bluetooth;

import org.brakepedal.server.util.ByteLookup;
import org.jetbrains.annotations.NotNull;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static org.brakepedal.server.support.bluetooth.BrakePedalStateFrame.HEAD;
import static org.brakepedal.server.support.bluetooth.BrakePedalStateFrame.TAIL;

/**
 * Date: 2024/4/1 14:35
 *
 * @author Huanyu Mark
 */
public class DefaultBtFrameDecoder implements BtFrameDecoder, Closeable {
    private static final int frameLength = 15;

    private static final int tailIndex = frameLength-2;

    private static final int crcCodeIndex = frameLength - 3;
    private final ByteBuffer buffer = ByteBuffer.allocate(frameLength-1);
    private final ScheduledThreadPoolExecutor scheduler;

    private int readCount = 0;

    private volatile ScheduledFuture<?> abortTask;

    private final int abortSecond;

    private volatile boolean aborted = false;
    public DefaultBtFrameDecoder(int abortSecond, ScheduledThreadPoolExecutor scheduler) {
        this.scheduler = scheduler;
        this.abortSecond = abortSecond;
    }

    @Override
    public @NotNull BtFrame decode(InputStream in) throws IOException {
        byte[] frame = buffer.array();
        while (true) {
            startAbort();
            if(in.read(frame, readCount, 1) < 0) {
                throw new IOException("No Byte Readable");
            }
            if(aborted) {
                ByteLookup.print("Abort frame:", frame);
                clear();
                continue;
            }
            cancelAbort();

            ByteLookup.print("["+readCount+"] Read:", frame[readCount]);
            ++readCount;
            if(readCount == buffer.capacity()) {
                if(frame[tailIndex] != TAIL) {
                    ByteLookup.print("Not Match Tail:", frame);
                    clearAndReadNewFrame();
                    continue;
                }
//                var crcCode = crc(frame);
//                var frameCrcCode = frame[crcCodeIndex];
//                if(crcCode != frameCrcCode) {
//                    System.out.println("crc code not match, expect: "+crcCode+" actual: "+frameCrcCode);
//                    ByteLookup.print("Not Match Frame:", frame);
//                    clearAndReadNewFrame();
//                    continue;
//                }
                return createFrame();
            }
            if(readCount == 1 && frame[0] != HEAD) {
                ByteLookup.print("Not Match Head", frame[0]);
                clearAndReadNewFrame();
            }
        }
    }

    private BtFrame createFrame(){
        printByteFrame();
        buffer.position(0);
        var builder = BrakePedalStateFrame.builder();
        buffer.get();
        builder.acc(readUnsignedShort())
                .pressure(readUnsignedShort())
                .year(readUnsignedShort())
                .month(readUnsignedByte())
                .day(readUnsignedByte())
                .hour(readUnsignedByte())
                .minute(readUnsignedByte())
                .second(readUnsignedByte());
        clearAndReadNewFrame();
        return builder.build();
    }

    private int readUnsignedShort(){
        short s = buffer.getShort();
        return s & 0xFFFF;
    }

    private int readUnsignedByte(){
        byte b = buffer.get();
        return b & 0xFF;
    }

    private void clear(){
        readCount = 0;
        buffer.clear();
    }

    private void clearAndReadNewFrame(){
        clear();
        System.out.println("---New Frame---");
    }

    private void startAbort(){
        if(abortTask != null) {
            abortTask.cancel(true);
        }
        aborted = false;
        abortTask = scheduler.schedule(()->{
            if(readCount == 0) {
                return;
            }
            System.out.println("frame timeout");
            clear();
            aborted = true;
        },abortSecond,TimeUnit.SECONDS);
    }

    private void cancelAbort(){
        if(abortTask == null) {
            return;
        }
        abortTask.cancel(true);
    }

    private static byte crc(byte[] data){
        byte crc = 0;
        for (byte b : data) {
            crc ^= b;
            for(int i = 0; i < 8; ++i) {
                if((crc & 0x80) > 0) {
                    crc = ((byte) ((crc << 1) ^ 0x07));
                } else {
                    crc <<= 1;
                }
            }
        }
        return crc;
    }

    private void printByteFrame(){
        ByteLookup.print("Frame Read", buffer.array());
    }

    @Override
    public void close(){
        if(abortTask == null){
            return;
        }
        abortTask.cancel(true);
    }
}
